﻿Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.Text

Public Class frmMain
    Private udpPort As Integer = 65535
    Public chatPort As Integer = 65534

    Private selfHostName As String
    Private selfIP As String
    Private selfDisplayname As String

    Private mapOfClients As Hashtable
    Public mapOfChatWindows As Hashtable

    Private ListThread As Thread
    Private chatListenerThread As Thread
    Private tcpListener As TcpListener
    Dim udpListener As UdpClient

    Private keepListeningForChatRequests As Boolean = True
    Private keepListeningTOUDP As Boolean = True

    Delegate Sub UpdateListCallback(ByVal operation As String, ByVal strValue As String)

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        sysTrayIcon.Visible = False

        Me.Location = My.Settings.WindowPosition
        Me.Size = My.Settings.WindowSize
        Me.selfDisplayname = My.Settings.displayName

        Me.txtDisplayName.Text = Me.selfDisplayname
        If Me.txtDisplayName.Text = "Unknown User" Then
            enableControls(True)
        Else
            enableControls(False)
        End If

        'thread that listens to UDP broadcasts that indicate whoz online
        ListThread = New Thread(New ThreadStart(AddressOf ListeningToUDP))
        ListThread.Priority = ThreadPriority.BelowNormal
        ListThread.IsBackground = True

        'thread that listens to chat requests from other users and initiates a chat window
        chatListenerThread = New Thread(New ThreadStart(AddressOf listeningToChatRequests))
        chatListenerThread.Priority = ThreadPriority.BelowNormal
        ListThread.IsBackground = True

        'start both threads
        ListThread.Start() 'Starts the thread that listens for UDP broadcasts to know about online clients
        chatListenerThread.Start() 'Starts the thread that listens for chat requests

        'start broadcasting status updates on UDP
        Timer1.Enabled = True

        selfHostName = Dns.GetHostName
        selfIP = GetIPAddress()
        Console.WriteLine("Your Machine Name = " & selfHostName)
        Console.WriteLine("Your IP = " & selfIP)

        mapOfClients = New Hashtable()
        mapOfChatWindows = New Hashtable()

        My.Application.SaveMySettingsOnExit = True


    End Sub

    Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        Dim offlinestring As String = " Has gone Offline."
        keepListeningTOUDP = False
        keepListeningForChatRequests = False
        'ListThread.Abort()

        'stop broadcasting status updates on UDP
        Timer1.Enabled = False

        My.Settings.WindowPosition = Me.Location
        My.Settings.WindowSize = Me.Size
        My.Settings.Save()

        sendClientStatus("OFFLINE")

        Console.WriteLine("Offline status sent. Invoking END()")

        End
    End Sub

    Private Sub enableControls(ByVal status As Boolean)
        Me.txtDisplayName.Enabled = status
        Me.btSetDisplayName.Enabled = status
        Me.lstOnlineClients.Enabled = Not status
    End Sub

    Public Function GetIPAddress() As String
        Dim oAddr As System.Net.IPAddress
        Dim sAddr As String
        With Dns.GetHostByName(selfHostName)
            oAddr = New System.Net.IPAddress(.AddressList(0).Address)
            sAddr = oAddr.ToString
        End With
        GetIPAddress = sAddr
    End Function

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        sendClientStatus("ONLINE")
    End Sub

    'send status to other clients
    Private Sub sendClientStatus(ByVal status As String)
        Try
            Dim broadcastIP As IPAddress = IPAddress.Broadcast
            Dim udpSender As New UdpClient()
            Dim messageBuffer As Byte() = Encoding.ASCII.GetBytes(GetIPAddress() & "|" & My.Settings.displayName & "|" & status) ' get your own IP address
            Dim ep As New Net.IPEndPoint(broadcastIP, udpPort)

            udpSender.EnableBroadcast = True
            udpSender.Send(messageBuffer, messageBuffer.Length, ep)
            udpSender.Close()
        Catch ex As Exception
            Console.WriteLine(ex.ToString)
        End Try
    End Sub

    'The below method listens to broadcasts, to know if a client has come online/gone offline
    Private Sub ListeningToUDP()
        Dim groupEP As New IPEndPoint(IPAddress.Broadcast, udpPort)
        Dim bytes As Byte()
        Dim recievedString As String = ""
        Dim displayName As String = ""
        Dim arrRecievedString(3) As String
        Dim removeIP As String

        udpListener = New UdpClient(udpPort)
        udpListener.EnableBroadcast = True

        While keepListeningTOUDP
            Try
                bytes = udpListener.Receive(groupEP)
                recievedString = Encoding.ASCII.GetString(bytes, 0, bytes.Length)
                arrRecievedString = recievedString.Split("|")

                displayName = arrRecievedString(1) & "<" & arrRecievedString(0) & ">"

                If Not arrRecievedString(0).Equals(selfIP) Then
                    If arrRecievedString(2).Equals("OFFLINE") Then
                        If (lstOnlineClients.Items.Contains(displayName)) Then
                            updateList("REMOVE", displayName)
                            If mapOfClients.ContainsKey(arrRecievedString(0)) Then
                                mapOfClients.Remove(arrRecievedString(0))
                            End If
                        End If
                    ElseIf arrRecievedString(2).Equals("ONLINE") Then
                        If Not lstOnlineClients.Items.Contains(displayName) Then
                            updateList("ADD", displayName)
                            If mapOfClients.ContainsKey(arrRecievedString(0)) Then
                                removeIP = arrRecievedString(0)

                                For Each itm In lstOnlineClients.Items
                                    If itm.ToString.IndexOf("<" & removeIP & ">") > 0 Then
                                        updateList("REMOVE", lstOnlineClients.GetItemText(itm))
                                        Exit For
                                    End If
                                Next
                            End If
                            mapOfClients.Remove(arrRecievedString(0))
                            mapOfClients.Add(arrRecievedString(0), arrRecievedString(1))
                        End If
                    End If
                End If
            Catch ex As SocketException
                keepListeningTOUDP = False
                udpListener.Close()
                Console.WriteLine("exception occured in UDPListener>> " & ex.ToString())
            End Try
        End While
    End Sub

    Private Sub listeningToChatRequests()
        Dim chatTcpClient As TcpClient
        Dim sizeOfBuffer As Integer = 1024
        Dim recieveBuffer(sizeOfBuffer - 1) As Byte
        Dim frmChatWindow As frmChat
        Dim chatIPEndPt As IPEndPoint
        Dim chatRecIP As String
        'Dim formList As New List(Of ChatWindow)

        Try
            tcpListener = New TcpListener(chatPort)
            tcpListener.Start()
            While keepListeningForChatRequests
                'wait for new chat request
                chatTcpClient = tcpListener.AcceptTcpClient()
                chatIPEndPt = chatTcpClient.Client.RemoteEndPoint
                chatRecIP = chatIPEndPt.Address.ToString
                'create a new chat window
                frmChatWindow = New frmChat(chatTcpClient, mapOfClients.Item(chatRecIP), chatRecIP)
                frmChatWindow.mainFormInstance = Me
                'maintain list of open chat windows
                mapOfChatWindows.Add(chatRecIP, frmChatWindow)
                Application.Run(frmChatWindow)
                frmChatWindow.Focus()
            End While
        Catch ex As Exception
            keepListeningForChatRequests = False
            MessageBox.Show("Exception occured in recieving chat>> " & ex.ToString)
        End Try
    End Sub

    'update the list of online/offline clients
    Private Sub updateList(ByVal operation As String, ByVal strValue As String)
        If lstOnlineClients.InvokeRequired Then
            Dim d As New updateListCallback(AddressOf updateList)
            Me.Invoke(d, New Object() {operation, strValue})
        Else
            If operation.Equals("ADD") Then
                lstOnlineClients.Items.Add(strValue)
            ElseIf operation.Equals("REMOVE") Then
                lstOnlineClients.Items.Remove(strValue)
            End If
        End If
    End Sub

    Private Sub btSetDisplayName_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btSetDisplayName.Click
        Me.selfDisplayname = Me.txtDisplayName.Text
        My.Settings.displayName = Me.txtDisplayName.Text
        enableControls(False)
    End Sub

    Private Sub lstOnlineClients_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstOnlineClients.DoubleClick
        Dim chatWindow As frmChat
        Dim selectedRecipient As String
        Dim chatRecIP As String
        Dim chatRecName As String
        Dim chatTcpClient As TcpClient

        If lstOnlineClients.SelectedIndex > -1 Then
            selectedRecipient = lstOnlineClients.SelectedItem.ToString
            chatRecName = selectedRecipient.Substring(0, selectedRecipient.IndexOf("<"))
            chatRecIP = selectedRecipient.Substring(selectedRecipient.IndexOf("<") + 1).TrimEnd(">")

            If mapOfChatWindows.ContainsKey(chatRecIP) Then
                'chat window with this user is already open so simply show it
                chatWindow = mapOfChatWindows.Item(chatRecIP)
            Else
                'create new chat window with user and show
                chatTcpClient = New TcpClient(chatRecIP, chatPort)
                chatWindow = New frmChat(chatTcpClient, chatRecName, chatRecIP)
                mapOfChatWindows.Add(chatRecIP, chatWindow)
                chatWindow.mainFormInstance = Me
            End If

            chatWindow.Show()
            chatWindow.Focus()
        Else
            MessageBox.Show("Please selected a person to chat with")
        End If
    End Sub

    Private Sub form_hide(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Resize
        If Me.WindowState = FormWindowState.Minimized Then
            sysTrayIcon.Visible = True
            Me.Visible = False
        End If
    End Sub

    Private Sub sysTrayIcon_MouseDoubleClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles sysTrayIcon.MouseClick
        Me.Visible = True
        Me.WindowState = FormWindowState.Normal
        sysTrayIcon.Visible = False
    End Sub
End Class
